package com.java.thread;

import java.util.concurrent.TimeUnit;

/**
 * 基于数组实现一个阻塞式队列
 * 1)数据存储结构：数组
 * 2)算法：FIFO
 * 3)线程策略：容器满了，向容器放数据的线程要阻塞，容器空了，从容器取数据的线程要阻塞
 * @param <T>
 */
class ArrayBlockQueueContainer<T>{

    /**基于此数组存储数据*/
    private Object[] array;
    /**基于size记录有效元素个数*/
    private int size;
    public ArrayBlockQueueContainer(){
        this(16);
    }
    public ArrayBlockQueueContainer(int cap){
        this.array=new Object[cap];
    }

    /**
     * 向容器放数据，始终放在size位置
     * 1)放数据前要判断容器是否满了，假如满了，在放数据的线程要阻塞
     * 2)容器没满则向容器放数据、并且让有效元素的个数加1；
     * 3)唤醒在同一个监视器(同步锁)下阻塞的线程
     * @param t
     */
    public synchronized void put(T t){//同步锁为调用此方法的当前对象
     //1.放数据前要判断容器是否满了，假如满了，在放数据的线程要阻塞;
       while(array.length==size){
           try{this.wait();}catch (InterruptedException e){}
       }
     //2.容器没满则向容器放数据、并且让有效元素的个数加1；
       array[size++]=t;
     //3.唤醒在同一个监视器(同步锁)下阻塞的线程
        this.notifyAll();
    }

    /**
     * 从容器取数据，永远从0位置开始取
     * 1)取数据前要判断容器是否为空，假如空了，取数据的线程要阻塞
     * 2)容器有数据则从容器取数据、移动元素中的数据，并执行size减一操作；
     * 3)唤醒在同一个监视器(同步锁)下阻塞的线程(例如放数据的线程)
     */
    public synchronized T take() {//锁的是this
        //1.取数据前要判断容器是否为空，假如空了，取数据的线程要阻塞
        while(size==0){
            try{this.wait();}catch (InterruptedException e){}
        }
        //2)容器有数据则从容器取数据、移动元素中的数据，并执行size减一操作；
        T e=(T)array[0];
        System.arraycopy(array,1,array,0,size-1);
        size--;
        array[size]=null;
        //3)唤醒在同一个监视器(同步锁)下阻塞的线程(例如放数据的线程)
        this.notifyAll();
        return e;
    }
}

public class ArrayBlockQueueContainerTests {
    public static void main(String[] args) {
        ArrayBlockQueueContainer<Integer> queue=
                new ArrayBlockQueueContainer(3);
        new Thread(()->{//生产者
            int i=1;
            while(true){
                queue.put(i++);
            }
        }).start();

        new Thread(()->{//消费者
            while(true) {
                System.out.println(queue.take());
            }
        }).start();

    }
}
